/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.gwt.basics.client.widget;

import com.genia.toolbox.web.gwt.basics.client.CssNames;
import com.genia.toolbox.web.gwt.basics.client.GwtHelper;
import com.genia.toolbox.web.gwt.basics.client.bean.GwtFileInformation;
import com.genia.toolbox.web.gwt.basics.client.message.Messages;
import com.genia.toolbox.web.gwt.basics.client.rpc.AbstractAsynCallback;
import com.genia.toolbox.web.gwt.basics.client.support.GwtObjectUtils;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FormHandler;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormSubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormSubmitEvent;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.XMLParser;

/**
 * widget to upload a file.
 */
public class FileUploadWidget
    extends Composite
{

  /**
   * a {@link FormHandler} that count the number of successfull submits, to
   * allow back to work.
   * 
   */
  private static final class CountSuccessfullSubmitFormHandler
      implements FormHandler
  {

    /**
     * Fired when the form is submitted.
     * 
     * @param event
     *          an event object containing information about the form submission
     * @see com.google.gwt.user.client.ui.FormHandler#onSubmit(com.google.gwt.user.client.ui.FormSubmitEvent)
     */
    public void onSubmit(FormSubmitEvent event)
    {
      // Do nothing.
    }



    /**
     * Fired when a form has been submitted successfully.
     * 
     * @param event
     *          an event object containing information about the form submission
     * @see com.google.gwt.user.client.ui.FormHandler#onSubmitComplete(com.google.gwt.user.client.ui.FormSubmitCompleteEvent)
     */
    public void onSubmitComplete(FormSubmitCompleteEvent event)
    {
      GwtHelper.addSucessfullSubmit();
    }

  }

  /**
   * {@link FormHandler} to handle upload command.
   */
  private final class FileUploadFormHandler
      implements FormHandler
  {

    /**
     * Fired when the form is submitted.
     * 
     * @param event
     *          an event object containing information about the form submission
     * @see com.google.gwt.user.client.ui.FormHandler#onSubmit(com.google.gwt.user.client.ui.FormSubmitEvent)
     */
    public void onSubmit(FormSubmitEvent event)
    {
      showProgress();
    }



    /**
     * Fired when a form has been submitted successfully.
     * 
     * @param event
     *          an event object containing information about the form submission
     * @see com.google.gwt.user.client.ui.FormHandler#onSubmitComplete(com.google.gwt.user.client.ui.FormSubmitCompleteEvent)
     */
    public void onSubmitComplete(FormSubmitCompleteEvent event)
    {
      DeferredCommand.addCommand(new Command()
      {
        public void execute()
        {
          formPanel.removeFormHandler(fileUploadFormHandler);
        }
      });
      String results = event.getResults();
      try {
        Document document = XMLParser.parse(event.getResults());
        XMLParser.removeWhitespace(document);
        results = GwtHelper.removeMarkup(document);
      }
      catch (Exception e) {
      }
      if ("200".equals(results)) {
        setValue(handleProgessBar.getId().toString());
      }
      else {
        GwtHelper.error(Messages.UPLOAD_ERROR_MESSAGE);
        showUploadControl();
      }
    }
  }

  /**
   * the time used to handle the file upload.
   */
  private class HandleProgessBar
      extends Timer
  {
    /**
     * the identifier of the file being uploaded.
     */
    private Long id;

    /**
     * whether to continue to show progress, or to stop.
     */
    private boolean showProgress;



    /**
     * getter for the id property.
     * 
     * @return the id
     */
    public Long getId()
    {
      return id;
    }



    /**
     * getter for the showProgress property.
     * 
     * @return the showProgress
     */
    public boolean isShowProgress()
    {
      return showProgress;
    }



    /**
     * This method will be called when a timer fires. Override it to implement
     * the timer's logic.
     * 
     * @see com.google.gwt.user.client.Timer#run()
     */
    @Override
    public void run()
    {
      GwtHelper.getGwtFileUploadService().getUploadProgress(id, new AbstractAsynCallback<Integer>()
      {

        public void onSuccess(final Integer progress)
        {
          if (isShowProgress()) {
            progressBar.setProgress(progress);
            if (progress < 100) {
              schedule(100);
            }
          }
        }
      });
    }



    /**
     * setter for the id property.
     * 
     * @param id
     *          the id to set
     */
    public void setId(Long id)
    {
      this.id = id;
    }



    /**
     * setter for the showProgress property.
     * 
     * @param showProgress
     *          the showProgress to set
     */
    public void setShowProgress(boolean showProgress)
    {
      this.showProgress = showProgress;
    }
  }

  /**
   * The {@link ClickListener} when the upload button is pressed.
   */
  private final class UploadButtonClickListener
      implements ClickListener
  {

    /**
     * the callback method called when the button is pressed.
     * 
     * @param widget
     *          the button being pressed
     * @see com.google.gwt.user.client.ui.ClickListener#onClick(com.google.gwt.user.client.ui.Widget)
     */
    public void onClick(final Widget widget)
    {
      if (fileUpload.getFilename().trim().length() == 0) {
        return;
      }
      uploadButton.setEnabled(false);
      GwtHelper.getGwtFileUploadService().getNewIdentifier(new AbstractAsynCallback<Long>()
      {

        @Override
        public void onFailure(Throwable error)
        {
          ((HyperLinkButton) widget).setEnabled(true);
          super.onFailure(error);
        }



        public void onSuccess(Long identifier)
        {
          ((HyperLinkButton) widget).setEnabled(true);
          progressBar.setProgress(0);
          formPanel.setAction(GwtHelper.getContextPath() + FILE_UPLOAD_SERVLET_PATH + identifier);
          handleProgessBar.setId(identifier);
          formPanel.addFormHandler(fileUploadFormHandler);
          formPanel.submit();
        }

      });
    }
  }

  /**
   * the url to the upload servlet.
   */
  private static final String FILE_UPLOAD_SERVLET_PATH = "/gwtFileUpload?identifier=";

  /**
   * the url to the preview servlet.
   */
  private static final String FILE_PREVIEW_SERVLET_PATH = "/gwtFilePreview?identifier=";

  /**
   * the {@link FormHandler} that counts the number of successfull submits.
   */
  private static final FormHandler COUNT_SUCCESSFULL_SUBMIT_FORM_HANDLER = new CountSuccessfullSubmitFormHandler();

  /**
   * the current value of this widget. It is an identifier that must be checked
   * with the servlet to retrieve the real content of the file.
   */
  private String value;

  /**
   * the registered {@link ChangeListenerCollection}.
   */
  private final transient ChangeListenerCollection changeListeners = new ChangeListenerCollection();

  /**
   * the main panel of the {@link  com.google.gwt.user.client.ui.Composite}.
   */
  private final transient VerticalPanel vPanel = new VerticalPanel();

  /**
   * the panel presenting the upload control.
   */
  private final transient HorizontalPanel uploadPanel = new HorizontalPanel();

  /**
   * the panel presenting the progress bar control.
   */
  private final transient HorizontalPanel progressPanel = new HorizontalPanel();

  /**
   * the panel presenting the current selected file.
   */
  private final transient HorizontalPanel presentationPanel = new HorizontalPanel();

  /**
   * the form that allow us to upload the file.
   */
  private final transient FormPanel formPanel;

  /**
   * the {@link ProgressBar} to show file uploads.
   */
  private final transient ProgressBar progressBar = new ProgressBar(20, org.gwtwidgets.client.ui.ProgressBar.SHOW_TIME_REMAINING + org.gwtwidgets.client.ui.ProgressBar.SHOW_TEXT);

  /**
   * the upload button.
   */
  private final HyperLinkButton uploadButton = WidgetFactory.newHyperLinkButton(Messages.UPLOAD_MESSAGE, CssNames.FILE_UPLOAD_WIDGET_UPLOAD_BUTTON_CSS_STYLE);

  /**
   * the cancel button.
   */
  private final HyperLinkButton cancelButton = WidgetFactory.newHyperLinkButton(Messages.CANCEL_MESSAGE, CssNames.FILE_UPLOAD_WIDGET_CANCEL_BUTTON_CSS_STYLE);

  /**
   * the upload control.
   */
  private final FileUpload fileUpload = new FileUpload();

  /**
   * the {@link Timer} to handle the progress bar.
   */
  private final HandleProgessBar handleProgessBar = new HandleProgessBar();

  /**
   * the preview image of an uploaded file.
   */
  private final ImageExternalHyperLink image = new ImageExternalHyperLink();

  /**
   * the link to an uploaded file.
   */
  private final TextExternalHyperLink link = new TextExternalHyperLink();

  /**
   * the {@link FormHandler} to use.
   */
  private final FileUploadFormHandler fileUploadFormHandler = new FileUploadFormHandler();



  /**
   * constructor.
   */
  public FileUploadWidget()
  {
    GwtHelper.fillText(progressBar, Messages.UPLOADING_FILE_MESSAGE);

    formPanel = new FormPanel();
    formPanel.setMethod(FormPanel.METHOD_POST);
    formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
    formPanel.addFormHandler(COUNT_SUCCESSFULL_SUBMIT_FORM_HANDLER);
    formPanel.add(uploadPanel);

    fileUpload.setName("file");

    uploadPanel.add(fileUpload);

    uploadButton.addClickListener(new UploadButtonClickListener());
    uploadPanel.add(uploadButton);
    progressPanel.add(progressBar);
    progressPanel.add(cancelButton);
    
    cancelButton.addClickListener(new ClickListener() {
      public void onClick(Widget widget)
      {
        formPanel.removeFormHandler(fileUploadFormHandler);
        showUploadControl();
      }      
    });

    link.setTarget(ExternalHyperLink.TARGET_BLANK);
    image.setTarget(ExternalHyperLink.TARGET_BLANK);
    presentationPanel.add(image);
    presentationPanel.add(link);
    HyperLinkButton clearButton = WidgetFactory.newHyperLinkButton(Messages.REMOVE_UPLOADED_FILE_MESSAGE, CssNames.FILE_UPLOAD_WIDGET_REMOVE_BUTTON_CSS_STYLE);
    clearButton.addClickListener(new ClickListener()
    {
      public void onClick(Widget widget)
      {
        setValue(null);
      }
    });
    presentationPanel.add(clearButton);

    vPanel.add(formPanel);
    vPanel.add(progressPanel);
    vPanel.add(presentationPanel);

    showUploadControl();

    initWidget(vPanel);
  }



  /**
   * show the upload control.
   */
  private void showUploadControl()
  {
    handleProgessBar.setShowProgress(false);
    uploadButton.setEnabled(true);
    progressPanel.setVisible(false);
    presentationPanel.setVisible(false);
    uploadPanel.setVisible(true);
  }



  /**
   * show the progress bar.
   */
  private void showProgress()
  {
    handleProgessBar.schedule(100);
    handleProgessBar.setShowProgress(true);
    progressBar.setProgress(0);
    presentationPanel.setVisible(false);
    uploadPanel.setVisible(false);
    progressPanel.setVisible(true);

  }



  /**
   * show the preview file.
   * 
   * @param identifier
   *          the identifier of the file to preview
   */
  private void showPreview(final Long identifier)
  {
    handleProgessBar.setShowProgress(false);

    uploadPanel.setVisible(false);
    progressPanel.setVisible(false);

    GwtHelper.getGwtFileUploadService().getFileInformation(identifier, new AbstractAsynCallback<GwtFileInformation>()
    {

      public void onSuccess(final GwtFileInformation fileInformation)
      {
        if (fileInformation == null) {
          setValue(null);
          return;
        }
        String url = GwtHelper.getContextPath() + FILE_PREVIEW_SERVLET_PATH + identifier;
        if (fileInformation.getContentType().startsWith("image/")) {
          image.setImageUrl(url);
          image.setImageTitle(fileInformation.getName());
          image.setUrl(url);
          link.setVisible(false);
          image.setVisible(true);
        }
        else {
          link.setUrl(url);
          link.setText(fileInformation.getName());
          image.setVisible(false);
          link.setVisible(true);
        }

        presentationPanel.setVisible(true);
      }

    });
  }



  /**
   * add a listener that is called each time the value of this object change.
   * 
   * @param changeListener
   *          the callback
   */
  public void addChangeListener(ChangeListener changeListener)
  {
    changeListeners.add(changeListener);
  }



  /**
   * returns the current value of this item.
   * 
   * @return a <code>String</code> representing the current value of this item
   * @see com.genia.toolbox.web.gwt.form.client.widget.item.impl.AbstractSimpleValuedItemWidget#getValue()
   */
  public String getValue()
  {
    return value;
  }



  /**
   * set the current value of this item.
   * 
   * @param value
   *          a <code>String</code> representing the new value of this item
   * @see com.genia.toolbox.web.gwt.form.client.widget.item.impl.AbstractSimpleValuedItemWidget#setValue(java.lang.String)
   */
  public void setValue(String value)
  {
    if (GwtObjectUtils.nullSafeEquals(this.value, value)) {
      return;
    }
    this.value = value;
    if (value == null || "".equals(value)) {
      showUploadControl();
    }
    else {
      showPreview(Long.valueOf(value));
    }
    changeListeners.fireChange(this);
  }

}
